/*
 * Copyright (c) 2011, Julien Lamy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the project's author nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "mainwindow.h"

#include <algorithm>
#include <fstream>
#include <functional>
#include <queue>
#include <set>
#include <vector>

#include <QtGui>

#include "about.h"
#include "dataobject.h"
#include "filenameformat.h"
#include "filter.h"
#include "itunesdatabase.h"
#include "metatype.h"
#include "track.h"
#include "ui_mainwindow.h"
#include "transfer.h"

struct track_attribute : public std::unary_function<Track, QString>
{
    track_attribute(DataObjectType::Type attribute)
    : attribute(attribute)
    {
        // Nothing else
    }

    QString operator()(Track const & track) const
    {
        return track[this->attribute];
    }

    DataObjectType::Type attribute;
};

bool track_comparator(Track const & t1, Track const & t2)
{
    return (t1.track_number < t2.track_number);
}

MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent), ui(new Ui::MainWindow())
{
    ui->setupUi(this);

    ui->action_Open->setIcon(this->style()->standardIcon(QStyle::SP_DirIcon));
    ui->action_Transfer->setIcon(this->style()->standardIcon(QStyle::SP_ArrowForward));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::fill_tree(ITunesDataBase const & db)
{
    this->ui->treeWidget->clear();
    typedef std::set<QString> StringSet;

    StringSet const artists = unique_set(db.tracks.begin(), db.tracks.end(),
                                         track_attribute(DataObjectType::ARTIST));

    for(StringSet::const_iterator artists_it=artists.begin();
        artists_it!=artists.end(); ++artists_it)
    {
        QTreeWidgetItem* artist_item =
            new QTreeWidgetItem((QTreeWidget*)0, QStringList(*artists_it));

        filter::Filter<QString> const artist_filter(DataObjectType::ARTIST, *artists_it);
        std::vector<Track> artist_tracks;
        for(std::vector<Track>::const_iterator it=db.tracks.begin();
            it != db.tracks.end(); ++it)
        {
            if(artist_filter(*it))
            {
                artist_tracks.push_back(*it);
            }
        }

        StringSet const albums = unique_set(artist_tracks.begin(), artist_tracks.end(),
                                            track_attribute(DataObjectType::ALBUM));

        for(StringSet::const_iterator albums_it=albums.begin();
            albums_it!=albums.end(); ++albums_it)
        {
            std::vector<Track> album_tracks;
            filter::Filter<QString> const album_filter(DataObjectType::ALBUM, *albums_it);
            for(std::vector<Track>::const_iterator it=artist_tracks.begin();
                it != artist_tracks.end(); ++it)
            {
                if(album_filter(*it))
                {
                    album_tracks.push_back(*it);
                }
            }

            std::sort(album_tracks.begin(), album_tracks.end(), track_comparator);

            QTreeWidgetItem* album_item = new QTreeWidgetItem(artist_item, QStringList(*albums_it));

            QVariant data = qVariantFromValue(album_tracks);
            album_item->setData(0, Qt::UserRole, data);
        }

        this->ui->treeWidget->addTopLevelItem(artist_item);
    }
}

void MainWindow::fill_list(std::vector<Track> const & tracks)
{
    this->ui->listWidget->clear();
    for(std::vector<Track>::const_iterator it = tracks.begin();
        it != tracks.end(); ++it)
    {
        QString const track_name = (*it)[DataObjectType::TITLE];
        QString const label = QString("%1. %2")
                .arg(it->track_number, 2, 10, QChar('0'))
                .arg(track_name);

        QListWidgetItem* list_item = new QListWidgetItem(label, this->ui->listWidget);

        QVariant data = qVariantFromValue(*it);
        list_item->setData(Qt::UserRole, data);
    }
}

struct ProgressDialogUpdater
{
    QProgressDialog & progress_dialog;
    QString format;
    int index;

    ProgressDialogUpdater(QProgressDialog & progress_dialog, QString const & format)
    : progress_dialog(progress_dialog), format(format), index(-1)
    {
        // Nothing else.
    }

    bool operator()(Track const & track)
    {
        ++this->index;
        progress_dialog.setLabelText(track.filename(format));
        progress_dialog.setValue(this->index);

        return !progress_dialog.wasCanceled();
    }


};

void MainWindow::transfer(std::vector<Track> const & tracks,
                          QString const & destination_directory,
                          QString const & format)
{
    QProgressDialog progress_dialog("Transfering files ...", "Abort transfer",
                                    0, tracks.size(),
                                    this);
    progress_dialog.setWindowModality(Qt::WindowModal);
    progress_dialog.setMinimumDuration(0);
    progress_dialog.setAutoClose(false);

    ::transfer(tracks.begin(), tracks.end(),
        this->mount_point_,
        destination_directory, format,
        ProgressDialogUpdater(progress_dialog, format));

    progress_dialog.setValue(tracks.size());
}

QString const & MainWindow::mount_point() const
{
    return this->mount_point_;
}

void MainWindow::set_mount_point(QString const & mount_point)
{
    this->mount_point_ = mount_point;

    this->setWindowTitle(QApplication::instance()->applicationName() +
                         " (" + this->mount_point_ + ")");
}

void MainWindow::about()
{
    About about(this);
    about.exec();
}

void MainWindow::copy_itunes_database()
{
    QString const directory =
        QFileDialog::getExistingDirectory(this, tr("Select mount point"), "/Volumes");
    if(directory == "")
    {
        // User canceled
        return;
    }

    QDir db_location(directory);
    if(db_location.exists("iPod_Control/iTunes"))
    {
        db_location.cd("iPod_Control/iTunes");
    }
    if(!db_location.exists("iTunesDB"))
    {
        QMessageBox message_box;
        message_box.setText("No iTunes database found in "+db_location.absolutePath()+".");
        message_box.exec();
        return;
    }

    QString const destination =
        QFileDialog::getSaveFileName(this, tr("Select destination file"),
                                     QDir::homePath()+"/iTunesDB");
    if(destination == "")
    {
        // User canceled
        return;
    }

    QFile::copy(db_location.absoluteFilePath("iTunesDB"), destination);
}

void MainWindow::open_file()
{
    QString const directory =
        QFileDialog::getExistingDirectory(this, tr("Select mount point"),
#ifdef Q_OS_LINUX
        		"/"
#elif defined Q_OS_MAC
        		"/Volumes"
#elif defined Q_OS_WIN32
        		"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" // CLSID for "My Computer"
#else
        		QString::null
#endif
		);
    if(directory == "")
    {
        // User canceled
        return;
    }

    QDir db_location(directory);
    if(db_location.exists("iPod_Control/iTunes"))
    {
        db_location.cd("iPod_Control/iTunes");
    }
    if(!db_location.exists("iTunesDB"))
    {
        QMessageBox message_box;
        message_box.setText("No iTunes database found in "+db_location.absolutePath()+".");
        message_box.exec();
        return;
    }

    this->set_mount_point(QDir::cleanPath(db_location.absoluteFilePath("../..")));

    ITunesDataBase db;
    std::ifstream db_stream(db_location.absoluteFilePath("iTunesDB").toLocal8Bit().constData(),
    						std::ifstream::binary|std::ifstream::in);
    try
    {
        db_stream >> db;
    }
    catch(std::exception const & e)
    {
        QMessageBox message_box;
        message_box.setText(db_location.absoluteFilePath("iTunesDB")+" is not an iTunes database.");
        message_box.exec();
        return;
    }
    if(db.albums.empty())
    {
        db.update_albums();
    }
    if(db.artists.empty())
    {
        db.update_artists();
    }

    this->fill_tree(db);
}

void MainWindow::tree_item_selection_changed()
{
    QList<QTreeWidgetItem*> items = this->ui->treeWidget->selectedItems();
    if(items.empty())
    {
        return;
    }

    QTreeWidgetItem* item = items.first();
    QVariant const data = item->data(0, Qt::UserRole);
    if(data.isNull())
    {
        this->ui->listWidget->clear();
        return;
    }

    std::vector<Track> const tracks = data.value<std::vector<Track> >();
    this->fill_list(tracks);
}

void MainWindow::transfer()
{
    FilenameFormat format_dialog(this);
    if(format_dialog.exec() == QDialog::Rejected)
    {
        return;
    }

    std::vector<Track> tracks;

    QList<QTreeWidgetItem*> const items = this->ui->treeWidget->selectedItems();
    for(QList<QTreeWidgetItem*>::const_iterator items_it = items.begin();
        items_it != items.end(); ++items_it)
    {
        std::queue<QTreeWidgetItem*> queue;
        queue.push(*items_it);
        while(!queue.empty())
        {
            QTreeWidgetItem* item = queue.front();
            queue.pop();

            QVariant const data = item->data(0, Qt::UserRole);
            if(!data.isNull())
            {
                std::vector<Track> const item_tracks = data.value<std::vector<Track> >();
                tracks.reserve(tracks.size()+item_tracks.size());
                std::copy(item_tracks.begin(), item_tracks.end(), std::back_inserter(tracks));
            }

            for(int i=0; i<item->childCount(); ++i)
            {
                queue.push(item->child(i));
            }
        }
    }

    this->transfer(tracks, format_dialog.output_directory(), format_dialog.format());
}
